package com.tetris;

import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.view.View;

import androidx.annotation.NonNull;

import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

// 游戏
@SuppressLint("Range")
class Game implements Callback {
    // 状态
    public enum Status {
        END,    // 已结束
        PAUSED, // 暂停中
        PLAYING // 游戏中
    }

    // 定时器任务类
    private static class GameTimerTask extends TimerTask {
        private final Handler mCallback;    // 回调
        
        /**
         * 构造方法。
         * @param callback  回调
         */        
        public GameTimerTask(Callback callback) {
            mCallback = new Handler(callback);
        }

        /**
         * 定时器事件的响应方法。
         */        
        @Override
        public void run() {
            mCallback.sendEmptyMessage(Const.UM_TIMER);
        }
    }

    private final Handler mCallback;                // 回调
    private Block mCurBlock;                        // 当前方块
    private int mDifficulty;                        // 难度
    private boolean mEnableMusic;                   // 音乐开关
    private final View mGameBoard;                  // 游戏区域
    private final Canvas mGameBoardDrawer;          // 游戏区域绘图者
    private final MediaPlayer mMusicPlayer;         // 音乐播放者
    private Block mNextBlock;                       // 下一个方块
    protected final Paint mPaint;                   // 绘图相关信息
    private final View mPreview;                    // 预览区域
    private final Canvas mPreviewDrawer;            // 预览区域绘图者
    private final Random mRandom;                   // 随机数生成器
    private int mScore;                             // 得分
    private Status mStatus;                         // 状态
    private final Timer mTimer;                     // 定时器
    private TimerTask mTimerTask;                   // 定时器任务
    private int mTop;                               // 有方块的最高行
    public static ColorGrid[][] sExistentBlocks;    // 累积的方块

    /**
     * 构造方法。
     * @param callback          回调
     * @param gameBoard         游戏区域
     * @param gameBoardDrawer   游戏区域绘图者
     * @param preview           预览区域
     * @param previewDrawer     预览区域绘图者
     */
    public Game(Callback callback, View gameBoard, Canvas gameBoardDrawer, View preview, Canvas previewDrawer) {
        // 初始化成员
        mCallback = new Handler(callback);
        mDifficulty = 1;
        mEnableMusic = false;
        mGameBoard = gameBoard;
        mGameBoardDrawer = gameBoardDrawer;
        mMusicPlayer = MediaPlayer.create(gameBoard.getContext(), R.raw.music);
        mMusicPlayer.setLooping(true);
        mPaint = new Paint();
        mPreview = preview;
        mPreviewDrawer = previewDrawer;
        mRandom = new Random();
        mScore = 0;
        mStatus = Status.END;
        mTimer = new Timer();
        mTop = Const.GAME_BOARD_GRID_COUNT_V + 1;

        // 画游戏区域
        this.drawGameBoard();

        // 画预览区域
        this.drawPreview();
    }

    /**
     * 检查是否存在满行。
     */
    private void checkRow() {
        // 更新"有方块的最高行"
        final int top = mCurBlock.getTop();
        if (top < mTop) {
            mTop = top;
        }

        int released = 0;   // 消除的行数
        final int bottom = mCurBlock.getBottom();

        // 从有方块的最上面一行，到当前方块所占的最下一行，逐行检查
        for (int i = mTop; i <= bottom; i++) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                // 如果该行有空的，就跳过该行
                if (!sExistentBlocks[i][j].mUsed) {
                    break;
                }
                // 如果该行满了，消除该行
                if (j == Const.GAME_BOARD_GRID_COUNT_H - 1) {
                    this.releaseRow(i);
                    released++;
                }
            }
        }

        if (released > 0) {
            // 因为消行了，所以需要重绘
            this.drawGameBoard();
            this.drawExistentBlock();

            // 计算得分
            mScore += released * released * 100;
            
            // 发送通知
            Message message = Message.obtain();
            message.what = Const.UM_SCORE;
            message.arg1 = mScore;
            mCallback.sendMessage(message);

            // 计算难度
            if (mScore / Const.DIFF_BASE + 1 > mDifficulty && mDifficulty < Const.DIFF_MAX) {
                // 增加难度
                mDifficulty++;
                mTimerTask.cancel();
                mTimerTask = new GameTimerTask(this);
                final long elapse = Const.TIMER_INIT - Const.TIMER_GROW * (mDifficulty - 1);
                mTimer.schedule(mTimerTask, elapse, elapse);

                // 发送通知
                message = Message.obtain();
                message.what = Const.UM_DIFFICULTY_UP;
                message.arg1 = mDifficulty;
                mCallback.sendMessage(message);
            }
        }
    }

    /**
     * 随机创建一种方块。
     * @return 方块
     */
    private Block createRandBlock() {
        final int random = Math.abs(mRandom.nextInt() % 7) + 1;

        switch (random) {
            case 1:
                return new Block1();
            case 2:
                return new Block2();
            case 3:
                return new Block3();
            case 4:
                return new Block4();
            case 5:
                return new Block5();
            case 6:
                return new Block6();
            case 7:
                return new Block7();
        }

        return new Block1();
    }

    /**
     * 画累积的方块。
     */
    private void drawExistentBlock() {
        for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                if (sExistentBlocks[i][j].mUsed) {
                    mPaint.setColor(sExistentBlocks[i][j].mColor);
                    mGameBoardDrawer.drawRect(Const.GRID_SIZE * j, Const.GRID_SIZE * i, Const.GRID_SIZE * (j + 1), Const.GRID_SIZE * (i + 1), mPaint);
                    Block.drawEdge(mGameBoardDrawer, Const.GRID_SIZE * j, Const.GRID_SIZE * i, mPaint);
                }
            }
        }
    }

    /**
     * 画游戏区域。
     */
    private void drawGameBoard() {
        mPaint.setColor(Const.GAME_BOARD_COLOR);
        mGameBoardDrawer.drawRect(0, 0, mGameBoard.getWidth(), mGameBoard.getHeight(), mPaint);
        mGameBoard.invalidate();
    }

    /**
     * 画预览区域。
     */
    private void drawPreview() {
        mPaint.setColor(Const.PREVIEW_COLOR);
        mPreviewDrawer.drawRect(0, 0, mPreview.getWidth(), mPreview.getHeight(), mPaint);
        if (mNextBlock != null) {
            mNextBlock.enterPreview(mPreview, mPreviewDrawer);
        }
        mPreview.invalidate();
    }

    /**
     * 游戏结束。
     * @param isWon  胜利则为 true，否则为 false
     */
    private void gameOver(boolean isWon) {
        // 将状态设置为已结束
        mStatus = Status.END;

        // 停止定时器
        mTimerTask.cancel();

        // 停止播放音乐
        if (mEnableMusic) {
            try {
                mMusicPlayer.stop();
                mMusicPlayer.prepare();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 发送通知
        final Message message = Message.obtain();
        message.what = Const.UM_GAME_OVER;
        message.obj = isWon;
        mCallback.sendMessage(message);
    }

    /**
     * 获取游戏状态。
     * @return 游戏状态
     */
    public Status getStatus() {
        return mStatus;
    }

    /**
     * 自定义事件的响应方法。
     * @param message   事件
     * @return 事件在此被处理则返回 true，否则返回 false
     */
    @Override
    public boolean handleMessage(@NonNull Message message) {
        this.moveBlockDown();
        return true;
    }

    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    private boolean isLost() {
        return (mTop == 0 && sExistentBlocks[0][4].mUsed);
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private boolean isWon() {
        return mScore >= Const.WIN_NUMBER;
    }

    /**
     * 方块下落。
     */
    public void moveBlockDown() {
        if (mStatus != Status.PLAYING) {
            return;
        }

        final boolean canMoveDown = mCurBlock.moveDown();
        if (!canMoveDown) {
            // 检查是否存在满行
            this.checkRow();

            // 判断是否胜利
            if (this.isWon()) {
                this.gameOver(true);
                return;
            }

            // 判断是否失败
            if (this.isLost()) {
                this.gameOver(false);
                return;
            }

            // 产生新的方块
            this.nextBlock();
        }
    }

    /**
     * 方块左移。
     */
    public void moveBlockLeft() {
        if (mStatus == Status.PLAYING) {
            mCurBlock.moveLeft();
        }
    }

    /**
     * 方块右移。
     */
    public void moveBlockRight() {
        if (mStatus == Status.PLAYING) {
            mCurBlock.moveRight();
        }
    }

    /**
     * 产生新的方块。
     */
    private void nextBlock() {
        // 创建当前方块
        if (mCurBlock != null) {
            mCurBlock = mNextBlock;
        } else {
            mCurBlock = this.createRandBlock();
        }

        // 创建下一个方块
        mNextBlock = this.createRandBlock();

        // 当前方块进入游戏区域
        mCurBlock.enterGameBoard(mGameBoard, mGameBoardDrawer);

        // 下一个方块进入预览区域
        this.drawPreview();
    }

    /**
     * 游戏暂停。
     */
    public void pause() {
        if (mStatus == Status.PLAYING) {
            // 停止定时器
            mTimerTask.cancel();

            // 停止播放音乐
            if (mEnableMusic) {
                mMusicPlayer.pause();
            }

            // 将状态设置为已结束
            mStatus = Status.PAUSED;
        } else if (mStatus == Status.PAUSED) {
            // 开启定时器
            mTimerTask = new GameTimerTask(this);
            final long elapse = Const.TIMER_INIT - Const.TIMER_GROW * (mDifficulty - 1);
            mTimer.schedule(mTimerTask, elapse, elapse);

            // 播放音乐
            if (mEnableMusic) {
                mMusicPlayer.start();
            }

            // 将状态设置为游戏中
            mStatus = Status.PLAYING;
        }
    }

    /**
     * 播放音乐。
     * @return 正在播放音乐则为 true，否则为 false
     */
    public boolean playMusic() {
        mEnableMusic = !mEnableMusic;

        if (mEnableMusic) {
            if (mStatus == Status.PLAYING) {
                mMusicPlayer.start();
            }
        } else {
            mMusicPlayer.pause();
        }

        return mEnableMusic;
    }

    /**
     * 消行。
     * @param row   行号
     */
    private void releaseRow(int row) {
        // 最上行
        if (row == 0) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                sExistentBlocks[row][j].mUsed = false;
            }
            mTop++;
            return;
        }

        // 非最上行
        for (int i = row; i > mTop; i--) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                sExistentBlocks[i][j].mUsed = sExistentBlocks[i - 1][j].mUsed;
                sExistentBlocks[i][j].mColor = sExistentBlocks[i - 1][j].mColor;
            }
        }

        for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            sExistentBlocks[mTop][j].mUsed = false;
        }

        mTop++;
    }

    /**
     * 游戏开始。
     */
    public void start() {
        // 画游戏区域
        this.drawGameBoard();

        // 画预览区域
        this.drawPreview();

        // 创建方块
        sExistentBlocks = new ColorGrid[Const.GAME_BOARD_GRID_COUNT_V][Const.GAME_BOARD_GRID_COUNT_H];
        for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                sExistentBlocks[i][j] = new ColorGrid();
            }
        }
        mCurBlock = null;
        mNextBlock = null;
        this.nextBlock();

        // 重置难度
        mDifficulty = 1;

        // 重置分数
        mScore = 0;

        // 开启定时器
        if (mTimerTask != null && mStatus != Status.PAUSED || mStatus != Status.END) {
            mTimerTask.cancel();
        }
        mTimerTask = new GameTimerTask(this);
        mTimer.schedule(mTimerTask, Const.TIMER_INIT, Const.TIMER_INIT);

        // 播放音乐
        if (mEnableMusic) {
            mMusicPlayer.start();
        }

        // 将状态设置为游戏中
        mStatus = Status.PLAYING;
    }

    /**
     * 方块变形。
     */
    public void transformBlock() {
        if (mStatus == Status.PLAYING) {
            mCurBlock.transform();
        }
    }
}
